package container.queue;

import lombok.Getter;
import lombok.Setter;
import util.ThreadUtil;

import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

public class T05_02_DelayQueue {
    /**
     * 业务场景二：具有过期时间的缓存
     * 向缓存添加内容时，给每一个 key 设定过期时间，系统自动将超过过期时间的 key 清除。
     * 这个场景中几个点需要注意：
     * * 当向缓存中添加 key-value 对时，如果这个 key 在缓存中存在并且还没有过期，需要用这个 key 对应的新过期时间
     * * 为了能够让 DelayQueue 将其已保存的 key 删除，需要重写实现 Delayed 接口添加到 DelayQueue 的 DelayedItem 的 hashCode 函数和 equals 函数
     * * 当缓存关闭，监控程序也应关闭，因而监控线程应当用守护线程
     */
    public static void main(String[] args) throws InterruptedException {
        Random random = new Random();
        int cacheNumber = 10;
        int liveTime = 0;
        Cache<String, Integer> cache = new Cache<>();

        for (int i = 0; i < cacheNumber; i++) {
            liveTime = random.nextInt(3000);
            System.out.println(i + "  " + liveTime);
            cache.put(i + "", i, random.nextInt(liveTime));

            if (random.nextInt(cacheNumber) > 7) {
                liveTime = random.nextInt(3000);
                System.out.println(i + "  " + liveTime);
                cache.put(i + "", i, random.nextInt(liveTime));
            }
        }

        Thread.sleep(3000);
        System.out.println("--------------");
    }

    /**
     * 利用延迟队列，来书写一个具有过期 key 效果的简单缓存，缓存使用 ConcurrentHashMap 实现
     */
    static class Cache<K, V> {
        public ConcurrentHashMap<K, V> map = new ConcurrentHashMap<>(); // 模拟装载缓存数据
        public DelayQueue<DelayedItem<K>> queue = new DelayQueue<>(); // 缓存即将要过期的 key 们

        public void put(K k, V v, long liveTime) { // 向缓存里面添加元素，可以指定key的存活时间
            V v2 = map.put(k, v);
            DelayedItem<K> tmpItem = new DelayedItem<>(k, liveTime);
            // 把旧的移除掉，若存在旧的话
            if (v2 != null) {
                queue.remove(tmpItem);
            }
            queue.put(tmpItem);
        }

        public Cache() { // 创建缓存对象的时候，开启一个守护线程，一直不停的去检查，阻塞队列里面是否有元素需要过期了移出来
            Thread t = new Thread(() -> {
                while (true) {
                    DelayedItem<K> delayedItem = queue.poll(); // 阻塞
                    if (delayedItem != null) {
                        map.remove(delayedItem.getT());
                        System.out.println(System.nanoTime() + " remove " + delayedItem.getT() + " from cache");
                    }
                    ThreadUtil.sleepMillis(300);
                }
            });
            t.setDaemon(true); //一定需要是守护线程
            t.start();
        }

    }

    @Getter
    @Setter
    static class DelayedItem<T> implements Delayed {
        private T t;
        private final long liveTime;
        private final long removeTime;

        public DelayedItem(T t, long liveTime) {
            this.setT(t);
            this.liveTime = liveTime;
            this.removeTime = TimeUnit.NANOSECONDS.convert(liveTime, TimeUnit.NANOSECONDS) + System.nanoTime();
        }

        @Override
        public int compareTo(Delayed o) {
            if (o == null) return 1;
            if (o == this) return 0;
            if (o instanceof DelayedItem) {
                DelayedItem<T> tmpDelayedItem = (DelayedItem<T>) o;
                if (liveTime > tmpDelayedItem.liveTime) {
                    return 1;
                } else if (liveTime == tmpDelayedItem.liveTime) {
                    return 0;
                } else {
                    return -1;
                }
            }
            // 按照 getDelay 来比较即可，因为有可能传进俩的对象并不是 DelayedItem 对象，而是别的 Delayed 对象
            long diff = getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);
            return diff > 0 ? 1 : diff == 0 ? 0 : -1;
        }

        @Override
        public long getDelay(TimeUnit unit) {
            return unit.convert(removeTime - System.nanoTime(), unit);
        }

        @Override
        public int hashCode() { // 必须重写，因为需要根据 key 移除，底层依赖 hashCode 和 equals
            return t.hashCode();
        }

        @Override
        public boolean equals(Object object) { // 必须重写，因为需要根据 key 移除，底层依赖 hashCode 和 equals
            if (object instanceof DelayedItem) {
                return object.hashCode() == hashCode();
            }
            return false;
        }
    }
}
